package ar.uba.fi.tonyvaliente.tree;

import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.logging.Logger;

import ar.uba.fi.tonyvaliente.utils.Pair;

/**
 * Clase principal del árbol, no se especifica si es en memoria o en disco.
 * Se parametrizan las clases de la clave y el valor
 * @author santiago
 *
 * @param <K> tipo de la clave
 * @param <V> tipo del valor
 */
public abstract class BTree <K extends Comparable<K>, V> {
	
	private static Logger log = Logger.getLogger(BTree.class.getName());
	
	protected int elementosPorNodo;           // máxima cantidad de elementos por nodo.
	private Set<BReference<K, V>> dirtyNodes; // conjunto de nodos modificados para actualizar
	protected BTreeNode<K, V> rootNode;       // nodo raiz del arbol
	

	/**
	 * Inicializa una instancia de la clase
	 * con una cantidad maxima de elementos por nodo
	 * que recibe como parametro
	 * @param elementosPorNodo cantidad maxima de elementos por nodo
	 */
	protected BTree() {
		dirtyNodes = new HashSet<BReference<K,V>>();
	}
	
	/**
	 * Inicializa una instancia de la clase
	 * con una cantidad maxima de elementos por nodo
	 * que recibe como parametro
	 * @param elementosPorNodo cantidad maxima de elementos por nodo
	 */
	protected BTree(int elementosPorNodo) {
		this();
		this.elementosPorNodo = elementosPorNodo;
	}
	
	/**
	 * Crea una referencia a un nodo de acuerdo al tipo de árbol
	 * que se está manejando.
	 * @return referencia que no apunta a ningun nodo
	 */
	protected abstract BReference<K, V> createReference();
	
	
	/**
	 * Añade el par clave valor al árbol.
	 * @param key
	 * @param value
	 */
	public void add(K key, V value) throws BTreeException {
		
		BTreeNode<K, V>.AddResult result = this.rootNode.add(key, value);
		
		if(result != null){
			BReference<K, V> newRoot = this.createReference();
			BTreeNode<K, V> newRootNode = new BTreeNode<K, V>(null, this/*, this.getElementosPorNodo()*/);
			newRootNode.setTree(this);
			
			newRootNode.getDatos().put(result.getKey(), result.getValue());
			newRootNode.getReferences().add(0, result.getLeftReference());
			newRootNode.getReferences().add(1, result.getRightReference());
			newRootNode.getReferences().remove(2);
			
			newRoot.setNode(newRootNode);
			this.setRootNode(newRoot);

			// Seteo el nuevo nodo raiz como padre de los resultantes del split
			result.getLeftReference().getNode().setParent(newRoot);
			result.getRightReference().getNode().setParent(newRoot);
			
			// Del nodo que queda como hijo derecho de la nueva raiz
			// tomo los hijos y les seteo el nuevo padre
			BReference<K, V> right = result.getRightReference();
			List<BReference<K, V>> refs = right.getNode().getReferences(); 
			for (BReference<K, V> ref: refs) {
				if (ref != null) {
					ref.getNode().setParent(right);
					dirtyNodes.add(ref);
				}
			}
		}
		
		for (BReference<K, V> reference : dirtyNodes) {
			reference.updateNode();
		}
		dirtyNodes.removeAll(dirtyNodes);

	}
	
	/**
	 * Devuelve un iterador de los pares clave-valor
	 * contenidos en el arbol
	 * @return iterador de los pares clave-valor
	 * contenidos en el arbol
	 */
	public Iterator<Pair<K,V>> iterator() {
		return new BTreeIterator<K,V>(this);
	}
	
	
	/**
	 * Dada una clave obtiene su valor.
	 * @param key clave a buscar
	 * @return valor asociado a la clave,
	 * null si no existe dicha clave en el arbol
	 */
	public V get(K key) throws BTreeException, NoSuchElementException{
		return this.rootNode.get(key);
	}


	/**
	 * Devuelve la cantidad maxima de elementos por nodo
	 * @return cantidad maxima de elementos por nodo
	 */
	public int getElementosPorNodo() {
		return elementosPorNodo;
	}


	/*public void setElementosPorNodo(int elementosPorNodo) {
		this.elementosPorNodo = elementosPorNodo;
	}*/


	protected Set<BReference<K, V>> getDirtyNodes(){
		return this.dirtyNodes;
	}
	
	protected BTreeNode<K, V> getRootNode() {
		return rootNode;
	}

	protected abstract void setRootNode(BReference<K,V> rootRef) throws BTreeException;


	/**
	 * cerramos los recursos utilizados.
	 * @throws BTreeException 
	 */
	public abstract void close() throws BTreeException;
	
}
